package edu.caltech.android;

import java.util.ArrayList;
import java.util.Timer;
import java.util.TimerTask;

import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.location.Location;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.telephony.TelephonyManager;
import android.util.Log;
import edu.caltech.android.comm.AppEngineComm;
import edu.caltech.android.comm.Comm;
import edu.caltech.android.database.RingBuffer;
import edu.caltech.android.eew.DiscActivity;
import edu.caltech.android.picking.GMPicker;
import edu.caltech.android.picking.Pick;
import edu.caltech.android.power.PowerSettings;
import edu.caltech.android.sensor.Localizer;
import edu.caltech.android.sensor.accel.AccelSample;
import edu.caltech.android.sensor.accel.Accelerometer;
import edu.caltech.csn.data.CsnMessages.HeartbeatResponse;
import edu.caltech.csn.data.CsnMessages.StatusType;

/**
 * This is a central point of the application, which gets sensor data, applies
 * the picking algorithm, acts on messages from the cloud, and supplies data to
 * the UI activities.
 * 
 * 
 * @author mfaulk
 * 
 */
public class ShakeAlertService extends Service {
	private static final String TAG = "ShakeAlertService";
	
	/**
	 *  broadcast intent action name
	 */
	public static final String PICK = "edu.caltech.android.ShakeAlertService.PICK";
	
	/**
	 * Pick intent extras key
	 */
	public static final String PICK_KEY = "pickKey";
	
	// Also check the BASE_URL in c2dm/AppEngineClient
	private final String BASE_URL = "http://seismocs.appspot.com";
	
	private final IBinder binder = new ShakeAlertServiceBinder();

	private Timer heartbeatTimer;
	private final int heartbeatMillis = 10 * 60 * 1000;			// converts minutes to milliseconds
	private GMPicker gmPicker;
	
	// schedule sending accelerometer data
	private Timer dataCollectionTimer = new Timer("dataCollectionTimer");

	// TODO: why are these instantiated differently?
	private AccelerationBroadcastReceiver accelReceiver;
	private final BroadcastReceiver mMessageReceiver = new C2DMBroadcastReceiver();
	
	private Localizer localizer;
	private Comm comm;

	private final boolean LOG_ACCEL = true;
	private final int ringBufferCapacity = 50 * 60 * 3;			// about 3 minutes total capacity
	private RingBuffer<AccelSample> accelRingBuffer;
	private final long dataCollectionDelayMillis = 30 * 1000; 	// 30 seconds   
	
	private PowerSettings powerManager; // may be null
	

	// ===========  Broadcast Receivers ================= //	

	/**
	 * receive accelerometer broadcasts
	 */
	private class AccelerationBroadcastReceiver extends BroadcastReceiver {

		@Override
		public void onReceive(Context context, Intent intent) {

			if (intent.getAction().equals(Accelerometer.ACCEL_SAMPLE)) {

				AccelSample sample = intent
						.getParcelableExtra(Accelerometer.ACCEL_SAMPLE_KEY);
				if (sample != null) {

					if(LOG_ACCEL){
						accelRingBuffer.add(sample);
					}
					
					// push this sample to the picking algorithm
					AccelSample m = new AccelSample(sample.x, sample.y,
							sample.z, sample.t);

					Pick pick = gmPicker.addData(m);
					if (pick != null) {
						reportPick(pick);
					}
				}
			}
		}
	} // end of class
	
	/**
	 * 
	 * @param eventLat Latitude, in degrees, of event.
	 * @param eventLon Longitude, in degrees, of event.
	 * @param eventMag Magnitude of event.
	 */
	private void launchEewUi(double eventLat, double eventLon, double eventMag) {
		Intent i = new Intent();
		i.setClass(this, DiscActivity.class);
		i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

		// Pass the user's current location, if available.
		Location location = localizer.getLocation();
		if (location != null) {
			Bundle extras = new Bundle();
			extras.putDouble("user_lat", location.getLatitude());
			extras.putDouble("user_lon", location.getLongitude());
			extras.putDouble("event_lat", eventLat);
			extras.putDouble("event_lon", eventLon);
			extras.putDouble("event_mag", eventMag);
			
			i.putExtras(extras);
		}
		
		startActivity(i);
	}
	
	// Listen for C2DM messages
	private class C2DMBroadcastReceiver extends BroadcastReceiver{
		@Override
		public void onReceive(Context context, Intent intent) {
			Log.i(TAG,"received C2DM");
			Bundle extras = intent.getExtras();
			double magnitude = 0.0;
			double lat = 0.0;
			double lon = 0.0;
			
			if(extras != null) {
				for(String key : extras.keySet()) {
					Log.i(TAG, "Intent contains key: " + key);
				}
				String txt = extras.getString("txt");
				Log.i(TAG,"Received txt: " + txt);
				String from = extras.getString("from");
				Log.i(TAG, "Received from: " + from);
				// txt should be magnitude,latitude,longitude
				String[] parts = txt.split(",");
				if(parts.length == 3) {
					magnitude = Double.parseDouble(parts[0]);
					lat = Double.parseDouble(parts[1]);
					lon = Double.parseDouble(parts[2]);
				} else {
					Log.w(TAG, "Received txt with " + parts.length + " parts");
				}
			}
			launchEewUi(lat, lon, magnitude);

//			CharSequence text = "CSN Alert Received!";
//			int duration = Toast.LENGTH_SHORT;
//			Toast toast = Toast.makeText(context, text, duration);
//			toast.show();
			

			// schedule data collection task
			dataCollectionTimer.schedule(new TimerTask(){
				public void run(){
					Log.i("DataCollectionTask", "running...");
					// send a heartbeat, with the waveform data
					// TODO: add data 
					Location location = localizer.getLocation();
					String parameterVersion = Double.toString(gmPicker
							.getParameterVersion());
					
					ArrayList<AccelSample> loggedMeasurements = accelRingBuffer.getContents();
					Log.d("DataCollectionTask", "Sending " + loggedMeasurements.size() + " AccelSamples");
					
					if (location != null) {
						comm.heartbeat(location.getLatitude(), location
								.getLongitude(), parameterVersion, loggedMeasurements);
					} else {
						Log.d(TAG, "No location fix. Using location (0,0)");
						comm.heartbeat(0.0, 0.0, parameterVersion, loggedMeasurements);
					}
				}
			} , dataCollectionDelayMillis);
		}
	
		
	}
	
//	private final BroadcastReceiver mMessageReceiver = new BroadcastReceiver() {
//		@Override
//		public void onReceive(Context context, Intent intent) {
//			CharSequence text = "CSN Alert Received!";
//			int duration = Toast.LENGTH_SHORT;
//
//			Toast toast = Toast.makeText(context, text, duration);
//			toast.show();
//		}
//	};

	// ===========  Lifecycle =========================== //
	
	@Override
	public void onCreate() {
		super.onCreate();

		gmPicker = new GMPicker();
		localizer = new Localizer(getApplicationContext());

		// ----- Wake Lock --------
		
		// --------- Accelerometer ------------- //
		accelRingBuffer = new RingBuffer<AccelSample>(ringBufferCapacity);
		
		Intent accelStartIntent = new Intent(ShakeAlertService.this,
				Accelerometer.class);
		startService(accelStartIntent);

		// register the acceleration broadcast receiver
		IntentFilter filter = new IntentFilter(Accelerometer.ACCEL_SAMPLE);
		accelReceiver = new AccelerationBroadcastReceiver();
		registerReceiver(accelReceiver, filter);

		// ---------- Registration ------------ //

		// Attempt to restore state from previous session
		// TODO: merge this with Prefs
		SharedPreferences mPrefs = getSharedPreferences(
				ShakeAlertApplication.PREFERENCE, 0);
		//
		String clientName = null;
		if (mPrefs.contains(ShakeAlertApplication.PREF_CLIENT_NAME_KEY)) {
			clientName = mPrefs.getString(
					ShakeAlertApplication.PREF_CLIENT_NAME_KEY, null);
		}
		//
		String clientID = null;
		if (mPrefs.contains(ShakeAlertApplication.PREF_CLIENT_ID_KEY)) {
			clientID = mPrefs.getString(
					ShakeAlertApplication.PREF_CLIENT_ID_KEY, null);
		}

		if ((clientName != null) && (clientID != null)) {
			Log.v(TAG, "Restoring client name and ID: " + clientName + ", "
					+ clientID);
			comm = new AppEngineComm(BASE_URL, clientName, clientID);
		} else {
			// Create a name for this client. This uses part of the device's
			// serial number.
			//
			String androidID = "00000";
			TelephonyManager tManager = (TelephonyManager) getApplicationContext()
					.getSystemService(Context.TELEPHONY_SERVICE);
			if (tManager != null) {
				String uid = tManager.getDeviceId();
				if (uid != null) {
					//Log.v(TAG, "Got uid " + uid);
					int substringLength = Math.min(uid.length(), 5);
					androidID = uid.substring(uid.length() - substringLength,
							uid.length());
				}
			}

			clientName = Build.MODEL + androidID;

			comm = new AppEngineComm(BASE_URL, clientName);

			Location location = localizer.getLocation();
			double lat = 0.0;
			double lon = 0.0;
			if (location != null) {
				lat = location.getLatitude();
				lon = location.getLongitude();
			}
			Log.v(TAG, "Registering with client name " + clientName);
			comm.register(lat, lon);
		}

		// ---------- schedule regular heart beats --------------- //

		heartbeatTimer = new Timer("heartbeatTimer");
		heartbeatTimer.scheduleAtFixedRate(new TimerTask() {
			public void run() {

				// check if there was a HeartbeatResponse from the last
				// Heartbeat message
				HeartbeatResponse heartbeatResponse = comm
						.pollHeartbeatResponse();
				if (heartbeatResponse != null) {
					if (heartbeatResponse.getStatus() == StatusType.SUCCESS) {
						// status message
						if (heartbeatResponse.hasMessage()) {
							Log.v(TAG, "heartbeat response message: "
									+ heartbeatResponse.getMessage());
						}

						// parameter update
						if (heartbeatResponse.hasParameterUpdate()) {
//							Log.d(TAG,"Begin parsing parameter update...");
//							try {
//								ParameterUpdate update = ParameterUpdate
//										.parseFrom(heartbeatResponse
//												.getParameterUpdate());
//								GMPickerParams gmPickerParams = parseParameterUpdate(update);
//
//							} catch (Exception e) {
//								Log.d(TAG, Log.getStackTraceString(e));
//							}
						}
						
						// log request
					}
				}

				Location location = localizer.getLocation();
				String parameterVersion = Double.toString(gmPicker
						.getParameterVersion());
				if (location != null) {
					comm.heartbeat(location.getLatitude(), location
							.getLongitude(), parameterVersion);
				} else {
					Log.d(TAG, "heartbeat aborted: no location fix.");
				}
			}
		}, heartbeatMillis, heartbeatMillis);

		// --------- Power Manager ---------- //

		//Log.v(TAG, "binding to PowerManager");
		Intent bindIntent = new Intent(ShakeAlertService.this,
				PowerSettings.class);
		bindService(bindIntent, powerConnection, Context.BIND_AUTO_CREATE);

		// --------- C2DM Alerts ------------- //

		// register for intents broadcast when a new C2DM message arrives
		registerReceiver(mMessageReceiver, new IntentFilter(
				C2DMReceiver.GOT_C2DM));

	}

	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		return super.onStartCommand(intent, flags, startId);
	}

	@Override
	public void onDestroy() {
		super.onDestroy();

		// ----- Accelerometer ------- //

		Log.d(TAG, "stopping accelerometer service...");
		Intent accelStopIntent = new Intent(ShakeAlertService.this,
				Accelerometer.class);
		stopService(accelStopIntent);
		unregisterReceiver(accelReceiver);

		// ----- Heartbeats ---------- //

		heartbeatTimer.cancel();

		// ----- Localizer ----------- //

		localizer.stop();

		// ----- Database ------------ //



		// --- Persist Registration --- //

		String clientName = comm.getClientName();
		String clientID = comm.getClientKey();
		if ((clientName != null) && (clientID != null)) {
			//Log.v(TAG, "Saving client name: " + clientName + ", key: " + clientID);
			SharedPreferences mPrefs = getSharedPreferences(
					ShakeAlertApplication.PREFERENCE, 0);
			//
			Editor editor = mPrefs.edit();
			editor
					.putString(ShakeAlertApplication.PREF_CLIENT_ID_KEY,
							clientID);
			editor.putString(ShakeAlertApplication.PREF_CLIENT_NAME_KEY,
					clientName);
			editor.commit();
		}
		
		// ----- PowerManager ----- //

		unbindService(powerConnection);

		// --------- C2DM Alerts ------------- //

		// unregister for intents broadcast when a new C2DM message arrives
		unregisterReceiver(mMessageReceiver);

		// ----- Wake Lock -------//
		//wakeLock.release();
	}

	/**
	 * Handles binding to the PowerManager
	 */
	private ServiceConnection powerConnection = new ServiceConnection() {
		public void onServiceConnected(ComponentName className, IBinder service) {
			// Called when the connection is made.
			powerManager = ((PowerSettings.PowerSettingsBinder) service)
					.getService();
		}

		public void onServiceDisconnected(ComponentName className) {
			// Received when the service unexpectedly disconnects.
			powerManager = null;
		}
	};

	

	/**
	 * 
	 * @param p
	 */
	private void reportPick(Pick p) {
//		Toast toast = Toast.makeText(getApplicationContext(), "Pick",
//				Toast.LENGTH_SHORT);
//		toast.show();
		
		// broadcast the pick
		Intent intent = new Intent(PICK);
		intent.putExtra(PICK_KEY, p);
		sendBroadcast(intent);

		Location location = localizer.getLocation();
		if (location != null) {
			comm.pick(p, location.getLatitude(), location.getLongitude());
		} else {
			Log.d(TAG, "Pick report aborted.");
		}
	}

	

	// TODO: new parameter update implementation

	@Override
	public IBinder onBind(Intent intent) {
		return binder;
	}

	//
	public class ShakeAlertServiceBinder extends Binder {
		public ShakeAlertService getService() {
			return ShakeAlertService.this;
		}
	}

	public String getClientID() {
		return comm.getClientKey();
	}

	public String getClientName() {
		return comm.getClientName();
	}

	public String getParameterVersion() {
		double paramVersion = gmPicker.getParameterVersion();
		return Double.toString(paramVersion);
	}

	public boolean gpsEnabled() {
		return localizer.gpsEnabled();
	}

	public boolean networkPositionEnabled() {
		return localizer.networkPositionEnabled();
	}

	// 
	public String getBatteryStatus() {
		String state = "unknown";
		if (powerManager != null) {
			state = powerManager.batteryStatus;
		}
		return state;
	}

} // end of class
